import 'dart:convert';
import 'dart:io';

import 'package:dio/dio.dart' as adio;
import 'package:dio/dio.dart';
import 'package:dio/io.dart';
import 'package:flutter/cupertino.dart';
import 'package:zhibao_flutter/util/check/check.dart';
import 'package:zhibao_flutter/util/check/regular.dart';
import 'package:zhibao_flutter/util/config/app_config.dart';
import 'package:zhibao_flutter/util/data/q1_data.dart';
import 'package:zhibao_flutter/util/func/log.dart';
import 'package:zhibao_flutter/util/func/tips_util.dart';
import 'package:zhibao_flutter/util/http/get_curl.dart';
import 'package:zhibao_flutter/util/http/http.dart';
import 'package:zhibao_flutter/util/http/http_req_util.dart';

export 'api.dart';
export 'base_request.dart';
export 'base_response_model.dart';
export 'base_view_model.dart';
export 'env.dart';
export 'handle.dart';

var _id = 0;

class Http {
  static const int CONNECT_TIMEOUT = 5000;
  static const int RECEIVE_TIMEOUT_SECOND = 20;
  static const int RECEIVE_TIMEOUT = RECEIVE_TIMEOUT_SECOND * 1000;

  static Future<AnHttpResponse?> doGetPin(String url) async {
    adio.BaseOptions options = adio.BaseOptions(
        connectTimeout: Duration(milliseconds: CONNECT_TIMEOUT),
        receiveTimeout: Duration(milliseconds: RECEIVE_TIMEOUT));

    Map result;
    adio.Dio dio = adio.Dio(options);
    dio.interceptors.add(GetCurlLoggerDioInterceptor(printOnSuccess: true));
    AnHttpResponse httpResponse;

    if (AppConfig.proxyUrl.length > 0) {
      proxy(dio);
    }

    try {
      adio.Response response = await dio.get(url,
          options: adio.Options(headers: await generatorHeader(url)));

      if (response.statusCode == HttpStatus.ok) {
        result = await response.data;
      } else {
        result = {};
      }

      httpResponse = AnHttpResponse(
          jsonEncode(result), response.statusCode, result['headers']);
    } on adio.DioError catch (e) {
      errorLog(url, e);
      return AnHttpResponse(
          jsonEncode({"Message": e.message}), e.response?.statusCode, {});
    }

    return httpResponse;
  }

  static Future<AnHttpResponse> doPost(String url, body) async {
    adio.BaseOptions options = adio.BaseOptions(
        connectTimeout: Duration(milliseconds: CONNECT_TIMEOUT),
        receiveTimeout: Duration(milliseconds: RECEIVE_TIMEOUT));

    Map result;
    adio.Dio dio = adio.Dio(options);
    dio.interceptors.add(GetCurlLoggerDioInterceptor(printOnSuccess: true));

    AnHttpResponse httpResponse;

    if (AppConfig.proxyUrl.length > 0) {
      proxy(dio);
    }

    try {
      var params = url.contains('user/register').not() ? body : null;

      var headers = await generatorHeader(url);

      Map<String, dynamic> paramsValue = Map.from(body);
      print("[${_id - 1}]HTTP_REQUEST_BODY_FULL::${json.encode(paramsValue)}");
      adio.Response response = await dio.post(
        url,
        data: paramsValue,
        queryParameters: params,
        options: adio.Options(headers: headers),
      );

      if (response.statusCode == HttpStatus.ok) {
        result = await response.data;
      } else {
        result = {};
      }

      httpResponse = AnHttpResponse(
          jsonEncode(result), response.statusCode, result['headers']);
    } on adio.DioException catch (e) {
      return dioErrHandle(e, url, body);
    }

    return httpResponse;
  }

  static Future<AnHttpResponse> doPatch(String url, body) async {
    adio.BaseOptions options = adio.BaseOptions(
        connectTimeout: Duration(milliseconds: CONNECT_TIMEOUT),
        receiveTimeout: Duration(milliseconds: RECEIVE_TIMEOUT));

    Map result;
    adio.Dio dio = adio.Dio(options);
    dio.interceptors.add(GetCurlLoggerDioInterceptor(printOnSuccess: true));
    AnHttpResponse httpResponse;

    if (AppConfig.proxyUrl.length > 0) {
      proxy(dio);
    }

    try {
      var params = url.contains('/login') ? body : null;

      var headers = await generatorHeader(url);

      adio.Response? response = await dio.patch(
        url,
        data: body,
        queryParameters: params,
        options: adio.Options(headers: headers),
      );

      if (response.statusCode == HttpStatus.ok) {
        result = await response.data;
      } else {
        result = {};
      }

      httpResponse = AnHttpResponse(
          jsonEncode(result), response.statusCode, result['headers']);
    } on adio.DioError catch (e) {
      return dioErrHandle(e, url, body);
    }

    return httpResponse;
  }

  static Future<AnHttpResponse> doFile(
    String url,
    body,
    ProgressCallback? onReceiveProgress,
    ProgressCallback? onSendProgress,
  ) async {
    adio.BaseOptions options = adio.BaseOptions(
        connectTimeout: Duration(milliseconds: 360 * 1000),
        receiveTimeout: Duration(milliseconds: 360 * 1000));

    Map result;
    adio.Dio dio = adio.Dio(options);
    dio.interceptors.add(GetCurlLoggerDioInterceptor(printOnSuccess: true));
    AnHttpResponse httpResponse;

    if (AppConfig.proxyUrl.length > 0) {
      proxy(dio);
    }

    try {
      File file = body['file'];
      MultipartFile fileData = await MultipartFile.fromFile(file.path);
      Map map = body;
      map['file'] = fileData;

      /// Make md5.
      // if (body['md5'] != null) {
      //   File md5File = body['md5'];
      //   try {
      //     final String fileChecksum =
      //         await Md5FileChecksum.getFileChecksum(filePath: md5File.path);
      //     map['md5'] = fileChecksum;
      //   } catch (exception) {
      //     debugPrint('Unable to generate file checksum: $exception');
      //   }
      // }

      LogUtil.d("HTTP_FILE_UPLOAD::$map");

      FormData formData = FormData.fromMap(Map.from(map));

      adio.Response response = await dio.post(
        url,
        data: formData,
        options: adio.Options(
          headers: await generatorHeader(
            url,
            contentTypeHeader: "multipart/form-data",
          ),
        ),
        onSendProgress: onSendProgress,
        onReceiveProgress: onReceiveProgress,
      );

      if (response.statusCode == HttpStatus.ok) {
        result = await response.data;
      } else {
        result = {};
      }

      httpResponse = AnHttpResponse(
          jsonEncode(result), response.statusCode, result['headers']);
    } on adio.DioError catch (e) {
      return dioErrHandle(e, url, body);
    }

    return httpResponse;
  }

  static Future<AnHttpResponse> doPut(String url, body) async {
    adio.BaseOptions options = adio.BaseOptions(
        connectTimeout: Duration(milliseconds: CONNECT_TIMEOUT),
        receiveTimeout: Duration(milliseconds: RECEIVE_TIMEOUT));

    Map result;
    adio.Dio dio = adio.Dio(options);
    dio.interceptors.add(GetCurlLoggerDioInterceptor(printOnSuccess: true));
    AnHttpResponse httpResponse;

    if (AppConfig.proxyUrl.length > 0) {
      proxy(dio);
    }

    try {
      adio.Response response = await dio.put(
        url,
        data: body,
        queryParameters: body,
        options: adio.Options(headers: await generatorHeader(url)),
      );

      if (response.statusCode == HttpStatus.ok) {
        result = await response.data;
      } else {
        result = {};
      }

      httpResponse = AnHttpResponse(
          jsonEncode(result), response.statusCode, result['headers']);
    } on adio.DioError catch (e) {
      return dioErrHandle(e, url, body);
    }

    return httpResponse;
  }

  static Future<AnHttpResponse> doDel(String url, body) async {
    adio.BaseOptions options = adio.BaseOptions(
        connectTimeout: Duration(milliseconds: CONNECT_TIMEOUT),
        receiveTimeout: Duration(milliseconds: RECEIVE_TIMEOUT));

    Map result;
    adio.Dio dio = adio.Dio(options);
    dio.interceptors.add(GetCurlLoggerDioInterceptor(printOnSuccess: true));
    AnHttpResponse httpResponse;

    if (AppConfig.proxyUrl.length > 0) {
      proxy(dio);
    }

    try {
      adio.Response response = await dio.delete(
        url,
        data: body,
        queryParameters: body,
        options: adio.Options(headers: await generatorHeader(url)),
      );

      if (response.statusCode == HttpStatus.ok) {
        result = await response.data;
      } else {
        result = {};
      }

      httpResponse = AnHttpResponse(
          jsonEncode(result), response.statusCode, result['headers']);
    } on adio.DioError catch (e) {
      return dioErrHandle(e, url, body);
    }

    return httpResponse;
  }

  static Future<AnHttpResponse> doGet(String url, body) async {
    adio.BaseOptions options = adio.BaseOptions(
        connectTimeout: Duration(milliseconds: CONNECT_TIMEOUT),
        receiveTimeout: Duration(milliseconds: RECEIVE_TIMEOUT));

    dynamic result;
    adio.Dio dio = adio.Dio(options);
    dio.interceptors.add(GetCurlLoggerDioInterceptor(printOnSuccess: true));
    AnHttpResponse httpResponse;

    if (AppConfig.proxyUrl.length > 0) {
      proxy(dio);
    }

    try {
      adio.Response response = await dio.get(
        url,
        queryParameters: body,
        options: adio.Options(headers: await generatorHeader(url)),
      );

      if (response.statusCode == HttpStatus.ok) {
        result = await response.data;
      } else {
        result = {};
      }

      var content = result is Map ? jsonEncode(result) : result.toString();
      httpResponse = AnHttpResponse(content, response.statusCode, {});
    } on adio.DioError catch (e) {
      return dioErrHandle(e, url, body);
    }

    return httpResponse;
  }

  static dioErrHandle(e, url, body) {
    var isNotNet =
        e.message.toString().contains('SocketException: Failed host lookup');

    /// Log print to console from http error.
    LogUtil.d("[dioErrHandle] $url");
    LogUtil.d("[dioErrHandle] [INFO]" + e.toString());

    if (e.type == DioErrorType.unknown && isNotNet) {
      throw ResponseModel.fromError(jsonEncode(TipsUtil.netError), 1, {});
    } else if (e.type == DioErrorType.connectionTimeout) {
      throw ResponseModel.fromError(jsonEncode(AppConfig.timeOutStr), 1, {});
    } else if (e.type == DioErrorType.sendTimeout) {
      throw ResponseModel.fromError(jsonEncode(AppConfig.timeOutStr), 1, {});
    } else if (e.type == DioErrorType.receiveTimeout) {
      throw ResponseModel.fromError(jsonEncode(AppConfig.timeOutStr), 1, {});
    } else if (e.type == DioErrorType.cancel) {
      throw ResponseModel.fromError(
          jsonEncode('The current request was canceled（$url）'), 1, {});
    }
    errorLog(url, e, body);
    throw ResponseModel.fromError(
        jsonEncode({"Message": e.message}), e.response.statusCode, {});
  }
}

proxy(adio.Dio dio) {
  (dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
      (client) {
    client.badCertificateCallback =
        (X509Certificate cert, String host, int port) {
      return Platform.isAndroid;
    };
    client.findProxy = (uri) {
      return "PROXY ${AppConfig.proxyUrl}";
    };
  };
}

Future<Map<String, dynamic>> generatorHeader(String url,
    {String? contentTypeHeader, String? refreshToken}) async {
  final id = _id++;

  Map<String, dynamic> header = {
    // // HttpHeaders.contentTypeHeader:
    // //     contentTypeHeader ?? 'application/json; charset=utf-8',
    // // "packageName": Q1DataRequest.packageName,
    // "app_name": AppConfig.appNameEn,
    // "device_id": Q1DataRequest.deviceId,
    // // "platform": Platform.isIOS ? 2 : "1",
    // "platform": HttpReqUtil.countPlatformCode(),
    // "version": HttpReqUtil.apiUseVersion(),
    // "device-model": Q1DataRequest.deviceModel,
    "Accept-Language": AppConfig.localeToHeader,
  };

  // if (Platform.isAndroid) {
  //   header['version-code'] = Q1DataRequest.versionCode;
  // }

  String? tokenStr = Q1Data.loginRspEntity?.token;
  String tokenHead = AppConfig.tokenHead;
  if (strNoEmpty(tokenStr)) {
    // header['authentication'] = "${refreshToken ?? tokenStr ?? ''}";
    header[tokenHead] = "${refreshToken ?? tokenStr ?? ''}";
  }

  /// 是否可以使用临时token
  /// [user/info] will use
  bool isCanUseTemp = url.contains('user/') || url.contains('common/');
  if (isCanUseTemp && !strNoEmpty(header[tokenHead])) {
    // header['authentication'] = Q1Data.tempToken;
    header[tokenHead] = Q1Data.tempToken;
  }

  debugPrint('HTTP_SIGN_HEADER::[$id]::${json.encode(header)}');
  return header;
}

Future<Map> generatorDeviceInfo(Map value) async {
  var deviceType = Platform.operatingSystem;

  Map newValue = {
    'deviceType': deviceType,
  };

  return newValue..addAll(value);
}

class AnHttpResponse {
  final String? body;
  final int? code;
  final Map? headers;

  AnHttpResponse(this.body, this.code, this.headers);
}
